home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Browsers, Managers & Extensions / Mozilla Weave 0.2.7 / latest-weave.xpi / modules / async.js next >
Text File  |  2008-07-16  |  12KB  |  355 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Bookmarks Sync.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Dan Mills <thunder@mozilla.com>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const EXPORTED_SYMBOLS = ['Async'];
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41. const Cr = Components.results;
  42. const Cu = Components.utils;
  43.  
  44. Cu.import("resource://weave/log4moz.js");
  45. Cu.import("resource://weave/util.js");
  46.  
  47. /*
  48.  * Asynchronous generator helpers
  49.  */
  50.  
  51. // Simple class to help us keep track of "namable" objects: that is,
  52. // any object with a string property called "name".
  53. function NamableTracker() {
  54.   this.__length = 0;
  55.   this.__dict = {};
  56. }
  57.  
  58. NamableTracker.prototype = {
  59.   get length() { return this.__length; },
  60.   add: function GD_add(item) {
  61.     this.__dict[item.name] = item;
  62.     this.__length++;
  63.   },
  64.   remove: function GD_remove(item) {
  65.     delete this.__dict[item.name];
  66.     this.__length--;
  67.   },
  68.   __iterator__: function GD_iterator() {
  69.     for (name in this.__dict)
  70.       yield name;
  71.   }
  72. }
  73.  
  74. let gCurrentId = 0;
  75. let gCurrentCbId = 0;
  76. let gOutstandingGenerators = new NamableTracker();
  77.  
  78. // The AsyncException class represents an exception that's been thrown
  79. // by an asynchronous coroutine-based function.  For the most part, it
  80. // behaves just like the "real" exception it wraps, only it adds some
  81. // embellishments that provide information about what other
  82. // asynchronous coroutine-based functions our current one was called
  83. // from.
  84. function AsyncException(asyncStack, exceptionToWrap) {
  85.   this._originalException = exceptionToWrap;
  86.   this._asyncStack = asyncStack;
  87.  
  88.   // Here we'll have our exception instance's prototype be dynamically
  89.   // generated; this ultimately allows us to dynamically "subclass"
  90.   // the exception we're wrapping at runtime.
  91.   this.__proto__ = {
  92.     __proto__: this._originalException,
  93.  
  94.     get asyncStack() this._asyncStack,
  95.     get originalException() this._originalException,
  96.  
  97.     addAsyncFrame: function AsyncException_addAsyncFrame(frame) {
  98.       this._asyncStack += ((this._asyncStack? "\n" : "") +
  99.                            Utils.formatFrame(frame));
  100.     },
  101.  
  102.     toString: function AsyncException_toString() {
  103.       return this._originalException.toString();
  104.     }
  105.   };
  106. }
  107.  
  108. function Generator(thisArg, method, onComplete, args) {
  109.   this._outstandingCbs = 0;
  110.   this._log = Log4Moz.Service.getLogger("Async.Generator");
  111.   this._log.level =
  112.     Log4Moz.Level[Utils.prefs.getCharPref("log.logger.async")];
  113.   this._thisArg = thisArg;
  114.   this._method = method;
  115.   this._id = gCurrentId++;
  116.   this.onComplete = onComplete;
  117.   this._args = args;
  118.   this._stackAtLastCallbackGen = null;
  119.  
  120.   gOutstandingGenerators.add(this);
  121.  
  122.   this._initFrame = skipAsyncFrames(Components.stack.caller);
  123. }
  124. Generator.prototype = {
  125.   get name() { return this._method.name + "-" + this._id; },
  126.   get generator() { return this._generator; },
  127.  
  128.   get cb() {
  129.     let caller = Components.stack.caller;
  130.     let cbId = gCurrentCbId++;
  131.     this._outstandingCbs++;
  132.     this._stackAtLastCallbackGen = caller;
  133.     this._log.trace(this.name + ": cb-" + cbId + " generated at:\n" +
  134.                     Utils.formatFrame(caller));
  135.     let self = this;
  136.     let cb = function(data) {
  137.       self._log.trace(self.name + ": cb-" + cbId + " called.");
  138.       self._cont(data);
  139.     };
  140.     cb.parentGenerator = this;
  141.     return cb;
  142.   },
  143.   get listener() { return new Utils.EventListener(this.cb); },
  144.  
  145.   get _thisArg() { return this.__thisArg; },
  146.   set _thisArg(value) {
  147.     if (typeof value != "object")
  148.       throw "Generator: expected type 'object', got type '" + typeof(value) + "'";
  149.     this.__thisArg = value;
  150.   },
  151.  
  152.   get _method() { return this.__method; },
  153.   set _method(value) {
  154.     if (typeof value != "function")
  155.       throw "Generator: expected type 'function', got type '" + typeof(value) + "'";
  156.     this.__method = value;
  157.   },
  158.  
  159.   get onComplete() {
  160.     if (this._onComplete)
  161.       return this._onComplete;
  162.     return function() {
  163.       //this._log.trace("Generator " + this.name + " has no onComplete");
  164.     };
  165.   },
  166.   set onComplete(value) {
  167.     if (value && typeof value != "function")
  168.       throw "Generator: expected type 'function', got type '" + typeof(value) + "'";
  169.     this._onComplete = value;
  170.   },
  171.  
  172.   get asyncStack() {
  173.     let cbGenText = "";
  174.     if (this._stackAtLastCallbackGen)
  175.       cbGenText = (" (last self.cb generated at " +
  176.                    Utils.formatFrame(this._stackAtLastCallbackGen) + ")");
  177.  
  178.     let frame = skipAsyncFrames(this._initFrame);
  179.  
  180.     return ("unknown (async) :: " + this.name + cbGenText + "\n" +
  181.             Utils.stackTraceFromFrame(frame, Utils.formatFrame));
  182.   },
  183.  
  184.   _handleException: function AsyncGen__handleException(e) {
  185.     if (e instanceof StopIteration) {
  186.       this._log.trace(this.name + ": End of coroutine reached.");
  187.       // skip to calling done()
  188.  
  189.     } else if (this.onComplete && this.onComplete.parentGenerator &&
  190.                this.onComplete.parentGenerator instanceof Generator) {
  191.       this._log.trace("[" + this.name + "] Saving exception and stack trace");
  192.       this._log.trace("Exception: " + Utils.exceptionStr(e));
  193.  
  194.         if (e instanceof AsyncException) {
  195.           // FIXME: attempt to skip repeated frames, which can happen if the
  196.           // child generator never yielded.  Would break for valid repeats (recursion)
  197.           if (e.asyncStack.indexOf(Utils.formatFrame(this._initFrame)) == -1)
  198.             e.addAsyncFrame(this._initFrame);
  199.         } else {
  200.           e = new AsyncException(this.asyncStack, e);
  201.         }
  202.  
  203.       this._exception = e;
  204.  
  205.     } else if (e.message && e.message == 'Cannot acquire lock (internal lock)') {
  206.       this._log.warn("Exception: " + Utils.exceptionStr(e));
  207.     } else {
  208.       this._log.error("Exception: " + Utils.exceptionStr(e));
  209.       this._log.debug("Async stack trace:\n" + Utils.stackTrace(e, Utils.formatFrame));
  210.     }
  211.  
  212.     // continue execution of caller.
  213.     // in the case of StopIteration we could return an error right
  214.     // away, but instead it's easiest/best to let the caller handle
  215.     // the error after a yield / in a callback.
  216.     if (!this._timer) {
  217.       this._log.trace("[" + this.name + "] running done() from _handleException()");
  218.       this.done();
  219.     }
  220.   },
  221.  
  222.   _detectDeadlock: function AsyncGen__detectDeadlock() {
  223.     if (this._outstandingCbs == 0)
  224.       this._log.warn("Async method '" + this.name +
  225.                      "' may have yielded without an outstanding callback.");
  226.   },
  227.  
  228.   run: function AsyncGen_run() {
  229.     this._continued = false;
  230.     try {
  231.       this._generator = this._method.apply(this._thisArg, this._args);
  232.       this.generator.next(); // must initialize before sending
  233.       this.generator.send(this);
  234.       this._detectDeadlock();
  235.     } catch (e) {
  236.       if (!(e instanceof StopIteration) || !this._timer)
  237.         this._handleException(e);
  238.     }
  239.   },
  240.  
  241.   _cont: function AsyncGen__cont(data) {
  242.     this._outstandingCbs--;
  243.     this._log.trace(this.name + ": resuming coroutine.");
  244.     this._continued = true;
  245.     try {
  246.       this.generator.send(data);
  247.       this._detectDeadlock();
  248.     } catch (e) {
  249.       if (!(e instanceof StopIteration) || !this._timer)
  250.         this._handleException(e);
  251.     }
  252.   },
  253.  
  254.   _throw: function AsyncGen__throw(exception) {
  255.     this._outstandingCbs--;
  256.     try { this.generator.throw(exception); }
  257.     catch (e) {
  258.       if (!(e instanceof StopIteration) || !this._timer)
  259.         this._handleException(e);
  260.     }
  261.   },
  262.  
  263.   // async generators can't simply call a callback with the return
  264.   // value, since that would cause the calling function to end up
  265.   // running (after the yield) from inside the generator.  Instead,
  266.   // generators can call this method which sets up a timer to call the
  267.   // callback from a timer (and cleans up the timer to avoid leaks).
  268.   // It also closes generators after the timeout, to keep things
  269.   // clean.
  270.   done: function AsyncGen_done(retval) {
  271.     if (this._timer) // the generator/_handleException called self.done() twice
  272.       return;
  273.     let self = this;
  274.     let cb = function() { self._done(retval); };
  275.     this._log.trace(this.name + ": done() called.");
  276.     if (!this._exception && this._outstandingCbs > 0)
  277.       this._log.warn("Async method '" + this.name +
  278.                      "' may have outstanding callbacks.");
  279.     this._timer = Utils.makeTimerForCall(cb);
  280.   },
  281.  
  282.   _done: function AsyncGen__done(retval) {
  283.     if (!this._generator) {
  284.       this._log.error("Async method '" + this.name + "' is missing a 'yield' call " +
  285.                       "(or called done() after being finalized)");
  286.       this._log.trace("Initial async stack trace:\n" + this.asyncStack);
  287.     } else {
  288.       this._generator.close();
  289.     }
  290.     this._generator = null;
  291.     this._timer = null;
  292.  
  293.     if (this._exception) {
  294.       this._log.trace("[" + this.name + "] Propagating exception to parent generator");
  295.       this.onComplete.parentGenerator._throw(this._exception);
  296.     } else {
  297.       try {
  298.         this._log.trace("[" + this.name + "] Running onComplete()");
  299.         this.onComplete(retval);
  300.       } catch (e) {
  301.         this._log.error("Exception caught from onComplete handler of " +
  302.                         this.name + " generator");
  303.         this._log.error("Exception: " + Utils.exceptionStr(e));
  304.         this._log.trace("Current stack trace:\n" +
  305.                         Utils.stackTrace(e, Utils.formatFrame));
  306.         this._log.trace("Initial async stack trace:\n" + this.asyncStack);
  307.       }
  308.     }
  309.     gOutstandingGenerators.remove(this);
  310.   }
  311. };
  312.  
  313. function skipAsyncFrames(frame) {
  314.   while (frame.name && frame.name.match(/^Async(Gen|)_/))
  315.     frame = frame.caller;
  316.   return frame;
  317. }
  318.  
  319. Async = {
  320.   get outstandingGenerators() { return gOutstandingGenerators; },
  321.  
  322.   // Use:
  323.   // let gen = Async.run(this, this.fooGen, ...);
  324.   // let ret = yield;
  325.   //
  326.   // where fooGen is a generator function, and gen is a Generator instance
  327.   // ret is whatever the generator 'returns' via Generator.done().
  328.  
  329.   run: function Async_run(thisArg, method, onComplete /* , arg1, arg2, ... */) {
  330.     let args = Array.prototype.slice.call(arguments, 3);
  331.     let gen = new Generator(thisArg, method, onComplete, args);
  332.     gen.run();
  333.     return gen;
  334.   },
  335.  
  336.   // Syntactic sugar for run().  Meant to be used like this in code
  337.   // that imports this file:
  338.   //
  339.   // Function.prototype.async = Async.sugar;
  340.   //
  341.   // So that you can do:
  342.   //
  343.   // let gen = fooGen.async(...);
  344.   // let ret = yield;
  345.   //
  346.   // Note that 'this' refers to the method being called, not the
  347.   // Async object.
  348.  
  349.   sugar: function Async_sugar(thisArg, onComplete  /* , arg1, arg2, ... */) {
  350.     let args = Array.prototype.slice.call(arguments, 1);
  351.     args.unshift(thisArg, this);
  352.     Async.run.apply(Async, args);
  353.   }
  354. };
  355.